package lk.icta.common.dao.impl;

import lk.icta.common.AppConstants;
import lk.icta.common.dao.GNDivisionDAO;
import lk.icta.common.dao.PreloadableDAO;
import lk.icta.common.entity.DSDivision;
import lk.icta.common.entity.GNDivision;
import lk.icta.common.ErrorCodes;
import lk.icta.user.entity.User;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.Query;
import java.util.*;

/**
 * @author Supun Nimesh Karunathilaka
 */
public class GNDivisionDAOImpl extends BaseDAO implements GNDivisionDAO, PreloadableDAO {

    // direct cache of objects by PK - dsDivisionUKey
    private final Map<Integer, GNDivision> gnDivisionsByPK = new HashMap<Integer, GNDivision>();
    // local caches indexed by districtUKey and dsDivisionUKey
    private final Map<Integer, Map<Integer, String>> siNames = new HashMap<Integer, Map<Integer, String>>();
    private final Map<Integer, Map<Integer, String>> enNames = new HashMap<Integer, Map<Integer, String>>();
    private final Map<Integer, Map<Integer, String>> taNames = new HashMap<Integer, Map<Integer, String>>();


    @Transactional(propagation = Propagation.MANDATORY)
    public void addGNDivision(GNDivision gnDivision, User user) {
        em.persist(gnDivision);
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void updateGNDivision(GNDivision gnDivision, User user) {
        em.merge(gnDivision);
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public GNDivision getGNDivisionByPK(int gnDivisionUKey) {
        return em.find(GNDivision.class, gnDivisionUKey);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<GNDivision> getGNDivisionByCodeAndDSDivision(int gnDivisionId, DSDivision dsDivision) {
        Query q = em.createNamedQuery("get.gnDivisions.by.code");
        q.setParameter("gnDivisionCode", gnDivisionId);
        q.setParameter("dsDivision", dsDivision);
        return q.getResultList();
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<GNDivision> getGNDivisionByAnyNameAndDSDivision(GNDivision gnDivision, int dsDivisionUKey, User user) {
        Query q = em.createNamedQuery("get.gnDivision.by.name.dsDivision");
        q.setParameter("dsDivision", dsDivisionUKey);
        q.setParameter("siName", gnDivision.getSiDivisionName());
        q.setParameter("enName", gnDivision.getEnDivisionName());
        q.setParameter("taName", gnDivision.getTaDivisionName());
        return q.getResultList();
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public List<GNDivision> getAllGNDivisionByDsDivisionKey(int dsDivisionId) {
        Query q = em.createNamedQuery("get.all.gnDivisions.by.dsDivisionId");
        q.setParameter("dsDivisionId", dsDivisionId);
        return q.getResultList();
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public List<GNDivision> getAllGNDivisionsByDistrictUKey(List<DSDivision> dsDivisions) {
        List<GNDivision> gnDivisions = new ArrayList<GNDivision>();
        Query q = em.createNamedQuery("get.all.gnDivisions.by.dsDivisionId");
        for (DSDivision ds : dsDivisions) {
            q.setParameter("dsDivisionId", ds.getId());
            gnDivisions.addAll(q.getResultList());
        }
        return gnDivisions;
    }

    public Map<Integer, String> getGNDivisionNamesByDsDivisionId(int dsDivisionId, String language, User user) {
        Map<Integer, String> result = null;
        if (AppConstants.SINHALA.equals(language)) {
            result = getGnDivisionNamesImpl(siNames, dsDivisionId);
        } else if (AppConstants.ENGLISH.equals(language)) {
            result = getGnDivisionNamesImpl(enNames, dsDivisionId);
        } else if (AppConstants.TAMIL.equals(language)) {
            result = getGnDivisionNamesImpl(taNames, dsDivisionId);
        } else {
            handleException("Unsupported language : " + language, ErrorCodes.INVALID_LANGUAGE);
        }
        // return sortGNDivision(result);
        return result;
    }

    private Map<Integer, String> sortGNDivision(Map<Integer, String> gnDivisionList) {
        List list = new LinkedList(gnDivisionList.entrySet());

        //sort list based on comparator
        Collections.sort(list, new Comparator() {
            public int compare(Object o1, Object o2) {
                return ((Comparable) ((Map.Entry) (o1)).getValue())
                        .compareTo(((Map.Entry) (o2)).getValue());
            }
        });

        //put sorted list into map again
        Map sortedMap = new LinkedHashMap();
        for (Iterator it = list.iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            sortedMap.put(entry.getKey(), entry.getValue());
        }
        return sortedMap;
    }

    private Map<Integer, String> getGnDivisionNamesImpl(
            Map<Integer, Map<Integer, String>> namesByDsDivision, int dsDivisionId) {

        Map<Integer, String> dsDivisionNames = namesByDsDivision.get(dsDivisionId);
        if (dsDivisionNames != null) {
            return dsDivisionNames;
        }
        logger.warn("No GN Divisions found for D.S Division : {}", dsDivisionId);
        return Collections.emptyMap();
    }

    /**
     * Loads all values from the database table into a cache
     */
    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public void preload() {

        Query query = em.createQuery("SELECT g FROM GNDivision g order by g.enDivisionName");
        List<GNDivision> results = query.getResultList();

        // Map<Integer, String> subMap = null;

        for (GNDivision d : results) {
            updateCache(d);
        }

        logger.debug("Loaded : {} DSDivisions from the database", results.size());
    }

    private void updateCache(GNDivision g) {
        Map<Integer, String> subMap;
        int dsDivisionId = g.getDsDivision().getId();
        int gnDivisionCode = g.getCode();
        int gnDivisionId = g.getId();

        gnDivisionsByPK.put(g.getId(), g);

        subMap = siNames.get(dsDivisionId);
        //append next gn division to the submap
        if (subMap == null) {
            subMap = new LinkedHashMap<Integer, String>();
            siNames.put(dsDivisionId, subMap);
        }
        subMap.put(gnDivisionId, gnDivisionCode + " : " + g.getSiDivisionName());

        subMap = enNames.get(dsDivisionId);
        if (subMap == null) {
            subMap = new LinkedHashMap<Integer, String>();
            enNames.put(dsDivisionId, subMap);
        }
        subMap.put(gnDivisionId, gnDivisionCode + " : " + g.getEnDivisionName());

        subMap = taNames.get(dsDivisionId);
        if (subMap == null) {
            subMap = new LinkedHashMap<Integer, String>();
            taNames.put(dsDivisionId, subMap);
        }
        subMap.put(gnDivisionId, gnDivisionCode + " : " + g.getTaDivisionName());
    }

}

